package com.cnzhy.spring.framework;

import com.cnzhy.spring.annotation.MyAutowired;
import com.cnzhy.spring.annotation.MyController;
import com.cnzhy.spring.annotation.MyRequestMapping;
import com.cnzhy.spring.annotation.MyService;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Pattern;

public class MyDispatcherServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    private static final Properties contextConfig = new Properties();

    private static final List<String> classNameList = new ArrayList<>();

    private static final Map<String, Object> ioc = new HashMap<>();

    private static final List<Handler> handlerMappingList = new ArrayList<>();

    public static List<Handler> getHandlerMappingList() {
        return handlerMappingList;
    }

    /**
     * 测试接口: http://localhost:8082/writing-spring/demo/query.json?name=zhaoyan
     * 使用maven启动tomcat7插件
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        try {
            doPost(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        // 6.等待请求阶段
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        Handler handler = getHandler(req);
        if (handler == null) {
            resp.getWriter().write("404 Not Found !");
            return;
        }
        // 获取方法的参数列表
        Class<?>[] paramTypes = handler.method.getParameterTypes();
        // 保存所有需要自动赋值的参数
        Object[] paramValues = new Object[paramTypes.length];
        Map<String, String[]> params = req.getParameterMap();
        for (Entry<String, String[]> param : params.entrySet()) {
            String value = Arrays.toString(param.getValue()).replace("\\[|\\]", "");
            // 如果找到配置的对象,则开始填充参数值
            if (!handler.paramIndexMapping.containsKey(param.getKey())) {
                continue;
            }
            int index = handler.paramIndexMapping.get(param.getKey());
            paramValues[index] = convert(paramTypes[index], value);
        }
        // 设置方法中的request和response对象
        int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
        paramValues[reqIndex] = req;
        int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
        paramValues[respIndex] = resp;
        handler.method.invoke(handler.controller, paramValues);
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerMappingList.isEmpty()) {
            return null;
        }
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "").replaceAll("/+", "/");
        for (Handler handler : handlerMappingList) {
            try {
                if (handler.pattern.matcher(url).matches()) {
                    return handler;
                }
            } catch (Exception e) {
                throw e;
            }
        }
        return null;
    }

    @Override
    public void init(ServletConfig config) {
        try {
            // 1.加载配置文件
            doLoadConfig(config.getInitParameter("contextConfigLocation"));
            // 2.扫描出所有需关联的类
            doScanner(contextConfig.getProperty("scanPackage"));
            // 3.初始化IOC容器,将相关联的类放入到IOC容器之中
            doInstance();
            // 4.实现依赖注入
            doAutoWired();
            // 5.初始化HandlerMapping
            initHandlerMapping();
            System.out.println("MyMVC inited");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Entry<String, Object> entry : ioc.entrySet()) {
            // HandlerMapping只针对@MyController
            Class<?> clazz = entry.getValue().getClass();
            if (clazz.isAnnotationPresent(MyController.class)) {
                String baseUrl = "";
                if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
                    MyRequestMapping myRequestMapping = clazz.getAnnotation(MyRequestMapping.class);
                    baseUrl = myRequestMapping.value();
                    // 不是所有的
                    Method[] methods = clazz.getMethods();
                    for (Method method : methods) {
                        if (method.isAnnotationPresent(MyRequestMapping.class)) {
                            baseUrl = baseUrl + method.getAnnotation(MyRequestMapping.class).value();
                            // http://localhost//////web//query.json
                            String regex = baseUrl = baseUrl.replaceAll("/+", "/");
                            Pattern pattern = Pattern.compile(regex);
                            handlerMappingList.add(new Handler(pattern, entry.getValue(), method));
                            System.out.println("Mapped:" + regex + "=" + method);
                        }
                    }
                }
            }
        }
        System.out.println("5.初始化HandlerMapping成功.");
    }

    private void doAutoWired() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Entry<String, Object> entry : ioc.entrySet()) {
            // 依赖注入就是给加个@MyAutoWired注解的字段赋值
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                // 字段都扫描出来了,但是不是所有的字段都要注入,只有有MyAutoWired注解的才需注入
                if (field.isAnnotationPresent(MyAutowired.class)) {
                    MyAutowired myAutowired = field.getAnnotation(MyAutowired.class);
                    String beanName = myAutowired.value();
                    if ("".equals(beanName)) {
                        beanName = field.getType().getName();
                        // 在反射机制中,类的私有成员变量也是可以随意访问.
                        field.setAccessible(true);
                        try {
                            field.set(entry.getValue(), ioc.get(beanName));
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        System.out.println("4.实现依赖注入成功.");
    }

    private void doInstance() {
        if (classNameList.isEmpty()) {
            return;
        }
        for (String className : classNameList) {
            try {
                Class<?> clazz = Class.forName(className);
                // 不是所有的类都要初始化的,只注册
                String beanName = "";
                if (clazz.isAnnotationPresent(MyController.class)) {
                    // 1.如果有自定义beanName,要以自定义的优先.
                    MyController myController = clazz.getAnnotation(MyController.class);
                    beanName = myController.value();
                } else if (clazz.isAnnotationPresent(MyService.class)) {
                    // 1.如果有自定义beanName,要以自定义的优先.
                    MyService MyService = clazz.getAnnotation(MyService.class);
                    beanName = MyService.value();
                }
                // 2.如果没有自定义的beanName, 默认是类名,但是首字母小写
                if ("".equals(beanName) || beanName == null) {
                    beanName = lowerFirstCase(clazz.getSimpleName());
                }
                if (clazz.isInterface()) {
                    continue;
                }
                Object instance = clazz.newInstance();
                ioc.put(beanName, instance);
                // 3.将实现保存到IOC容器中.
                Class<?>[] interfaces = clazz.getInterfaces();
                for (Class<?> i : interfaces) {
                    ioc.put(i.getName(), instance);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("3.初始化IOC容器,将相关联的类放入到IOC容器之中成功.");
    }

    private String lowerFirstCase(String str) {
        if (str != null & Character.isUpperCase(str.charAt(0))) {
            char[] chars = str.toCharArray();
            // 大写转小写
            chars[0] += 32;
            return String.valueOf(chars);
        }
        return str;
    }

    private void doScanner(String packageName) throws UnsupportedEncodingException {
        URL url = this.getClass().getClassLoader().getResource(packageName.replaceAll("\\.", "/"));
        File classDir = new File(URLDecoder.decode(url.getFile(), "UTF-8"));
        for (File classFile : classDir.listFiles()) {
            if (classFile.isDirectory()) {
                doScanner(packageName + "." + classFile.getName());
            } else if (classFile.isFile()) {
                String className = (packageName + "." + classFile.getName()).replace(".class", "");
                classNameList.add(className);
                System.out.println(className);
            }
        }
        System.out.println("2.扫描出所有需关联的类成功");
    }

    private void doLoadConfig(String location) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(location);
        try {
            contextConfig.load(is);
            System.out.println("1.加载配置文件成功");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public Object convert(Class<?> type, String value) {
        if (Integer.class == type) {
            return Integer.valueOf(value);
        }
        return value;
    }
}
